package ru.ifmo.ctddev.krotkov.bags;

import java.util.*;

class LinkedBag<T> extends AbstractCollection<T> {

	class LinkedBagIterator implements Iterator<T>{
		Iterator<T> it;
		T value;
		LinkedBag<T> obj;
		long version;

		@Override
		public boolean hasNext() {
			if (version < obj.time) {
				throw new ConcurrentModificationException();
			}
			return it.hasNext();
		}

		@Override
		public T next() {
			if (version < obj.time) {
				throw new ConcurrentModificationException();
			}
			value= it.next();
			return value;
		}

		@Override
		public void remove() {
			it.remove();
			times.get(value).remove(0);
			if (times.get(value).size() == 0) {
				times.remove(value);
			}
			obj.size--;
			obj.time++;
			version++;
		}
		
		public LinkedBagIterator(LinkedBag<T> obj) {
			it = obj.main.values().iterator();
			version = obj.time;
			this.obj = obj;
		}
	}
	
	LinkedHashMap<Long, T> main;
	HashMap<T, ArrayList<Integer>> times;
	long time;
	int size;
	
	public LinkedBag(Collection<? extends T> collection) {
		this();
		addAll(collection);
	}
	
	LinkedBag() {
		main = new LinkedHashMap<Long, T>();
		times = new HashMap<T, ArrayList<Integer>>();
		size = 0;
		time = 0;
	}

	@Override
	public int size() {
		return size;
	}
	
	@Override
	public boolean add(T arg) {
		time++;
		size++;
		main.put(time, arg);
		if (!times.containsKey(arg)) {
			times.put(arg, new ArrayList<Integer>());
		}
		times.get(arg).add(0);
		return true;
	};
	
	@Override
	public boolean remove(Object arg) {
		if (!times.containsKey(arg)) {
			return false;
		}
		main.remove(times.get(arg).get(0));
		times.get(arg).remove(0);
		if (times.get(arg).size() == 0) {
			times.remove(arg);
		}
		size--;
		time++;
		return true;
	}
	
	@Override
	public boolean contains(Object o) {
		return times.containsKey(o);
	}

	@Override
	public Iterator<T> iterator() {
		return new LinkedBagIterator(this);
	}
	
	@Override
	public void clear() {
		main = new LinkedHashMap<Long, T>();
		size = 0;
		times = new HashMap<T, ArrayList<Integer>>();
	}
}